#include "CNetwork.h"
#include "CTagGraph.h"
#include "CFile.h"
#include "CNode.h"
#include <algorithm>
#include <string>
#include <sstream>
#include <iostream>
#include <fstream>
#include <math.h>
#include <set>

using namespace std;


#ifdef _DEBUG
#define new DEBUG_NEW
#endif

/*CNetwork::CNetwork()
{
	ID = 0;
	pNodes = 20;
	time = 0.;
	string name = "Results0.txt";
	ofstream file;
	file.open(name.c_str(), ios::out);
	if (file.is_open())//clearing the file
		file.close();

	//TODO: Figure out if generic constructor is needed
}*/

CNetwork::CNetwork(int num, int physNodes, int regNodes, int superNodes, int files, int xMax, int yMax)
{
	ID = num;
	currentNodeID = 0;
	pNodes = physNodes;
	stringstream name;
	currentTime = 0.;
	name << "Results" << num << ".csv";
	ofstream file;
	file.open(name.str().c_str(), ios::out);
	if (file.is_open())//clearing the file
		file.close();
	f = files;

	physNodes -= regNodes + superNodes;

	cout << "Initializing nodes..." << endl;
	//Create the actual nodes... physical, regular peers (nodes), and super nodes
	vector< CNode > initNodes;
	for (int i=0; i<regNodes; i++)
	{
		initNodes.push_back(CNode( (CNode::Type)1, xMax, yMax, currentNodeID ));
		currentNodeID++;
	}
	for (int i=0; i<superNodes; i++)
	{
		initNodes.push_back(CNode( (CNode::Type)2, xMax, yMax, currentNodeID ));
		currentNodeID++;
	}
	for (int i=0; i<physNodes; i++)
	{
		initNodes.push_back(CNode( (CNode::Type)0, xMax, yMax, currentNodeID ));
		currentNodeID++;
	}

	//Create the network grid. This allows for easier nearest neighbor searches.
	for (int x=0; x<xMax; x++)
	{
		vector< loc > row;
		for (int y=0; y<yMax; y++)
		{
			//Make a grid location (struct defined in CNetwork.h)
			row.push_back(loc());
		}
		grid.push_back(row);
	}

	//Now put the nodes in the proper grid location
	for (int i=0; i<(int)initNodes.size(); i++)
	{
		double x = 0.;
		double y = 0.;
		initNodes[i].retXY(x,y);
		grid[(int)floor(x)][(int)floor(y)].nodes.push_back(initNodes[i]);
	}
	cout << "Grid created, making files..." << endl;

	CreateFiles();
	AssignFiles();
	MakeConnections();
}

CNetwork::~CNetwork()
{
	//deallocate resources, if needed
	//deallocate grid
	for (int i=0; i<(int)grid.size(); i++)
		grid[i].clear();
	grid.clear();
}

void CNetwork::Update(double time)
{
	currentTime = time;
	for (int i=0; i<(int)grid.size(); i++)
	{
		for (int j=0; j<(int)grid[i].size(); j++)
		{
			for (int n=0; n<(int)grid[i][j].nodes.size(); n++)
			{
				//Update, anything else? Need to pass in anything?
				grid[i][j].nodes[n].Update(time);
			}
		}
	}


	//time += 1.; //TODO: figure out time step... Most expensive calculation? What?
	// Just keep it constant... figure out how much work is done in that time
}


void CNetwork::End()
{
	//After sim done, print results to file
	stringstream name;
	name << "Results" << ID << ".csv";
	ofstream file;
	string filename = name.str();
	file.open(filename.c_str(), ios::out | ios::app);
	
	if (file.is_open())
	{
		file << "\"Node ID\",Bandwidth,\"Computation Time\",\"Mbits Transferred\",Communications,Neighbors,Children,X,Y" << endl;
		file.close();

		//Print all nodes and associated stats
		for (int i=0; i<(int)grid.size(); i++)
		{
			for (int j=0; j<(int)grid[i].size(); j++)
			{
				for (int n=0; n<(int)grid[i][j].nodes.size(); n++)
				{
					grid[i][j].nodes[n].printData(filename);
				}
			}
		}
	}
}



void CNetwork::CreateFiles()
{
	

	CTagGraph tags;
	cout << "Attempting tags..." << endl;
	tags.branches = tags.GenGraph(5, 0);
	int min = 4;

	//int forceStop = 100;
	//Create the specified number of files (f)
	for (int i=0; i<f; i++)
	{
		CFile newFile(i);
		//Give it a random branch of the tag tree, at min depth m
		CTagGraph *toAdd = NULL;
		int counter = 0;
		while (toAdd == NULL)
		{
			toAdd = tags.getBranch(0, min);
			counter++;
			if (counter == 5) //Maximum time to try finding a branch
				break;
		}
		newFile.setTags(toAdd);
		files.push_back(newFile);
		
		
		cout << ".";


		//forcestop--;
		//if forcestop == 100
	}

	cout << "Done making files. " << endl;
}



void CNetwork::MakeConnections()
{
	cout << "Making connections... ";// << endl;
	for (int i=0; i<(int)grid.size(); i++)
	{
		//cout << "1";
		for (int j=0; j<(int)grid[i].size(); j++)
		{
			//cout << "2";
			for (int n=0; n<(int)grid[i][j].nodes.size(); n++)
			{
				//cout << "3";
				//Now, find this node's closest 1-4 connections
				int N = rand() % 4 + 1;
				double x = -1., y = -1.;
				vector< CNode* > closestNodes;
				grid[i][j].nodes[n].retXY(x, y);
				if (x >= 0. && y >= 0.)
					closestNodes = findClosestN(N, i, j, 0, x, y);

				//Now that we have the closest N nodes, create those connections
				for (int cl=0; cl<(int)closestNodes.size(); cl++)
				{
					grid[i][j].nodes[n].addN(closestNodes[cl]);
					(*closestNodes[cl]).addN(&grid[i][j].nodes[n]);
				}
			}
		}
	}
	cout << "Done with connections!" << endl;
}

vector< CNode* > CNetwork::findClosestN(int n, int x, int y, int range, double oX, double oY)
{
	vector< CNode* > results;
	//First test if x, y is valid
	if (x < 0) x = 0;
	if (x >= (int)grid.size()) x = (int)grid.size()-1;
	if (y < 0) y = 0;
	if (y >= (int)grid[0].size()) y = (int)grid[0].size()-1;

	//Set up the vector of nodes to add to results
	vector< CNode* > test;
	vector<double> dists;

	int xmax = min(x+range, (int)grid.size());
	int ymax = min(y+range, (int)grid[0].size());
	for (int i=x; i<xmax; i++)
	{
		for (int j=y; j<ymax; j++)
		{
			for (int cur=0; cur<(int)grid[i][j].nodes.size(); cur++)
			{
				double curDist = grid[i][j].nodes[cur].getDist(oX, oY);
				bool added = false;
				for (int q=0; q<(int)dists.size(); q++)
				{
					if (curDist < dists[q])
					{
						//Add this node to the test vector
						test.insert(test.begin() + q, &grid[i][j].nodes[cur]);
						dists.insert(dists.begin() + q, curDist);
						if ((int)test.size() > n)
						{
							test.pop_back();//.erase(test.end()-1);
							dists.pop_back();//.erase(dists.end()-1);
						}
						added = true;
					}
				}

				if (!added && (int)test.size()<n)
				{
					test.push_back(&grid[i][j].nodes[cur]);
					dists.push_back(curDist);
				}
			}
		}
	}

	results = test;

	int size = (int)results.size();
	if (size < n)
	{
		vector< CNode* > add = findClosestN(n-size, x-1, y-1, range+2, oX, oY);;
		for (int i=0; i<(int)add.size(); i++)
			results.push_back(add[i]);
		return results;
	}
	else
		return results;
}




void CNetwork::AssignFiles()
{
	for (int i=0; i<(int)grid.size(); i++)
	{
		for (int j=0; j<(int)grid[i].size(); j++)
		{
			for (int n=0; n<(int)grid[i][j].nodes.size(); n++)
			{
				int rF = rand() % (int)(f * .1) + 1; //Give each user at least one file
				int rD = rand() % (int)(f * .2) + 1; //They want at least one other file
				vector<int> i1;
				for (int z=0; z<f; z++)
					i1.push_back(z);
				std::random_shuffle(i1.begin(), i1.begin());
				for (int s=0; s<rF; s++)
				{
					grid[i][j].nodes[n].files.push_back(files[s]);
				}
				for (int s=rF; s<rD+rF; s++)
				{
					grid[i][j].nodes[n].files.push_back(files[s]);
				}
			}
		}
	}
}